/*-------------------------------------------------------------------------
 *
 * path.c
 *	  便携式路径处理例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/port/path.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <ctype.h>
#include <sys/stat.h>
#ifdef WIN32
#ifdef _WIN32_IE
#undef _WIN32_IE
#endif
#define _WIN32_IE 0x0500
#ifdef near
#undef near
#endif
#define near
#include <shlobj.h>
#else
#include <unistd.h>
#endif

#include "mb/pg_wchar.h"
#include "pg_config_paths.h"


#ifndef WIN32
#define IS_PATH_VAR_SEP(ch) ((ch) == ':')
#else
#define IS_PATH_VAR_SEP(ch) ((ch) == ';')
#endif

#ifdef WIN32
static void debackslash_path(char *path, int encoding);
static int	pg_sjis_mblen(const unsigned char *s);
#endif
static void fc_make_relative_path(char *fc_ret_path, const char *fc_target_path,
							   const char *fc_bin_path, const char *fc_my_exec_path);
static char *fc_trim_directory(char *fc_path);
static void fc_trim_trailing_separator(char *fc_path);
static char *fc_append_subdir_to_path(char *fc_path, char *fc_subdir);


/*
 * skip_drive
 *
 * 在 Windows 上，路径可能以 "C:" 或 "//network/" 开头。跳过此部分，
 * 并指向路径的有效起始位置。
 */
#ifdef WIN32

static char * skip_drive(const char *path)
{
	if (IS_DIR_SEP(path[0]) && IS_DIR_SEP(path[1]))
	{
		path += 2;
		while (*path && !IS_DIR_SEP(*path))
			path++;
	}
	else if (isalpha((unsigned char) path[0]) && path[1] == ':')
	{
		path += 2;
	}
	return (char *) path;
}
#else

#define skip_drive(path)	(path)
#endif

/*
 *	has_drive_prefix
 *
 * 如果给定的路径名有驱动程序前缀，则返回 true。
 */
bool has_drive_prefix(const char *fc_path)
{
#ifdef WIN32
	return skip_drive(fc_path) != fc_path;
#else
	return false;
#endif
}

/*
 *	first_dir_separator
 *
 * 查找第一个目录分隔符的位置，如果未找到，则返回 NULL。
 */
char * first_dir_separator(const char *fc_filename)
{
	const char *fc_p;

	for (fc_p = skip_drive(fc_filename); *fc_p; fc_p++)
		if (IS_DIR_SEP(*fc_p))
			return unconstify(char *, fc_p);
	return NULL;
}

/*
 *	first_path_var_separator
 *
 * 查找第一个路径分隔符的位置（即在 Unix 上为 ':'，在 Windows 上为 ';'），
 * 如果未找到，则返回 NULL。
 */
char * first_path_var_separator(const char *fc_pathlist)
{
	const char *fc_p;

	/* skip_drive 不需要 */
	for (fc_p = fc_pathlist; *fc_p; fc_p++)
		if (IS_PATH_VAR_SEP(*fc_p))
			return unconstify(char *, fc_p);
	return NULL;
}

/*
 *	last_dir_separator
 *
 * 查找最后一个目录分隔符的位置，如果未找到，则返回 NULL。
 */
char * last_dir_separator(const char *fc_filename)
{
	const char *fc_p,
			   *fc_ret = NULL;

	for (fc_p = skip_drive(fc_filename); *fc_p; fc_p++)
		if (IS_DIR_SEP(*fc_p))
			fc_ret = fc_p;
	return unconstify(char *, fc_ret);
}


#ifdef WIN32

/*
 * 在给定路径中将 '\' 转换为 '/'，假设该路径
 * 是以指定编码表示的。
 */
static void debackslash_path(char *path, int encoding)
{
	char	   *p;

	/*
	 * 在支持的编码中，只有 Shift-JIS 有多字节字符
	 * 可以包含一个字节等于 '\' (0x5C)。因此，不使用
	 * 完全支持编码的转换，而对 SJIS 进行特殊处理。 
	 * （在这里调用 wchar.c 中的一般编码感知逻辑由于
	 * 各种原因不可行。）
	 */
	if (encoding == PG_SJIS)
	{
		for (p = path; *p; p += pg_sjis_mblen((const unsigned char *) p))
		{
			if (*p == '\\')
				*p = '/';
		}
	}
	else
	{
		for (p = path; *p; p++)
		{
			if (*p == '\\')
				*p = '/';
		}
	}
}

/*
 * SJIS 字符长度
 *
 * 这必须符合行为
 *		pg_encoding_mblen_bounded(PG_SJIS, s)
 * 特别是，与 src/common/wchar.c 中的 pg_sjis_mblen 版本不同，
 * 不允许调用者意外越过字符串结尾。
 */
static int pg_sjis_mblen(const unsigned char *s)
{
	int			len;

	if (*s >= 0xa1 && *s <= 0xdf)
		len = 1;				/* 1 字节的假名？ */
	else if (IS_HIGHBIT_SET(*s) && s[1] != '\0')
		len = 2;				/* 汉字？ */
	else
		len = 1;				/* 应该是 ASCII */
	return len;
}

#endif							/* WIN32 */


/*
 *	make_native_path - 在 WIN32 上，将路径中的 '/' 改为 '\'
 *
 *	这将撤销 debackslash_path 的 '\'-到-'/' 转换。
 *	我们在这里不需要担心编码，因为 '/' 在任何支持的编码中
 *	都不会出现在多字节字符的字节中。
 *
 *	这是必需的，因为 WIN32 COPY 是一个内部 CMD.EXE
 *	命令，并且不会以与外部命令相同的方式处理正斜杠。
 *	对 COPY 的第一个参数进行引用不会将正斜杠转换为反斜杠，但
 *	COPY 会正确处理第二个参数中的引用正斜杠。
 *
 *	COPY 仅在当前目录与第一个参数的目录相同时
 *	处理引用的正斜杠。
 */
void make_native_path(char *fc_filename)
{
#ifdef WIN32
	char	   *p;

	for (p = filename; *p; p++)
		if (*p == '/')
			*p = '\\';
#endif
}


/*
 * 此函数用于清理 Windows 上 cmd.exe 或 Msys 的路径。
 * 我们需要它们使用不带空格的文件名，短文件名是最安全的等效名，例如：
 *		C:/Progra~1/
 *
 * 目前，这仅用于我们可以假设为服务器安全编码的路径，
 * 所以不需要编码感知的变体。
 */
void cleanup_path(char *fc_path)
{
#ifdef WIN32
	/*
	 * 如果路径不存在，或者在此文件系统上已禁用短名称，
	 * GetShortPathName() 将失败。在这两种情况下，我们只返回
	 * 原始路径。这对于可能不存在的 --sysconfdir 特别有用。
	 */
	GetShortPathName(fc_path, fc_path, MAXPGPATH - 1);

	/* 将 '\' 替换为 '/' */
	/* 所有服务器安全编码在这里都是相同的，因此只使用 PG_SQL_ASCII */
	debackslash_path(fc_path, PG_SQL_ASCII);
#endif
}


/*
 * join_path_components - 合并两个路径组件，插入斜杠
 *
 * 如果任一给定的组件为空，则省略斜杠。
 *
 * ret_path 是输出区域（必须是大小为 MAXPGPATH）
 *
 * ret_path 可以与 head 相同，但不能与 tail 相同。
 */
void join_path_components(char *fc_ret_path,
					 const char *fc_head, const char *fc_tail)
{
	if (fc_ret_path != fc_head)
		strlcpy(fc_ret_path, fc_head, MAXPGPATH);

	/*
	 * 我们过去试图简化一些涉及 "." 和 ".." 的情况，但现在
	 * 我们只是留给后面的 canonicalize_path() 处理。
	 */

	if (*fc_tail)
	{
		/* 只有在 head 不为空时才用斜杠分隔 */
		snprintf(fc_ret_path + strlen(fc_ret_path), MAXPGPATH - strlen(fc_ret_path),
				 "%s%s",
				 (*(skip_drive(fc_head)) != '\0') ? "/" : "",
				 fc_tail);
	}
}


/* canonicalize_path 的状态机状态 */
typedef enum
{
	ABSOLUTE_PATH_INIT,			/* 刚过绝对路径的起始 '/'（和 Windows 驱动器名称如果有的话） */
	ABSOLUTE_WITH_N_DEPTH,		/* 我们在绝对路径中收集了 'pathdepth' 目录 */
	RELATIVE_PATH_INIT,			/* 在相对路径的开始 */
	RELATIVE_WITH_N_DEPTH,		/* 我们在相对路径中收集了 'pathdepth' 目录 */
	RELATIVE_WITH_PARENT_REF	/* 仅包含双点的相对路径 */
} canonicalize_state;

/*
 * canonicalize_path()
 *
 *	通过以下方式清理路径：
 *		o  使 Win32 路径使用 Unix 斜杠
 *		o  移除 Win32 的尾随引号
 *		o  移除尾随斜杠
 *		o  移除重复（相邻）分隔符
 *		o  移除 '.'（除非路径只简化为 '.'）
 *		o  自行处理 '..'，如有可能则移除
 *  就地修改路径。
 *
 *  这有两种变体：编码感知和非感知。非感知版本
 *  仅在服务器安全编码的字符串上安全使用。
 */
void canonicalize_path(char *fc_path)
{
	/* 所有服务器安全编码在这里都是相同的，因此只使用 PG_SQL_ASCII */
	canonicalize_path_enc(fc_path, PG_SQL_ASCII);
}

void canonicalize_path_enc(char *fc_path, int fc_encoding)
{
	char	   *fc_p,
			   *fc_to_p;
	char	   *fc_spath;
	char	   *fc_parsed;
	char	   *fc_unparse;
	bool		fc_was_sep = false;
	canonicalize_state fc_state;
	int			fc_pathdepth = 0;	/* 计数收集的常规目录名称 */
#ifdef FDD
	if (NULL == fc_path)
		return;
#endif
#ifdef WIN32

	/*
	 * Windows 命令处理器会接受适当引用的路径并使用
	 * 正斜杠，但对于混合使用正斜杠和反斜杠的情况会失败。
	 * 因此，首先将所有反斜杠转换为正斜杠。
	 */
	debackslash_path(fc_path, fc_encoding);

	/*
	 * 在 Win32 中，如果你这样做：prog.exe "a b" "\c\d\"，
	 * 系统会将 \c\d" 传递为 argv[2]，因此修剪掉尾随引号。
	 */
	fc_p = fc_path + strlen(fc_path);
	if (fc_p > fc_path && *(fc_p - 1) == '"')
		*(fc_p - 1) = '/';
#endif

	/*
	 * 移除路径上的尾随斜杠意味着我们不会得到难看的双
	 * 尾随斜杠。此外，Win32 无法 stat() 带有尾随
	 * 斜杠的目录。但不要移除前导斜杠。
	 */
	fc_trim_trailing_separator(fc_path);

	/*
	 * 移除相邻的重复分隔符
	 */
	fc_p = fc_path;
#ifdef WIN32
	/* 不要在 Win32 上移除前导双斜杠 */
	if (*fc_p)
		fc_p++;
#endif
	fc_to_p = fc_p;
	for (; *fc_p; fc_p++, fc_to_p++)
	{
		/* 处理许多相邻的斜杠，例如 "/a///b" */
		while (*fc_p == '/' && fc_was_sep)
			fc_p++;
		if (fc_to_p != fc_p)
			*fc_to_p = *fc_p;
		fc_was_sep = (*fc_p == '/');
	}
	*fc_to_p = '\0';

	/*
	 * 移除任何使用的 "."，并自行处理 ".."
	 *
	 * 注意，"/../.." 应简化为 "/"，而 "../.." 必须保持不变。
	 * 还要注意，我们希望 Windows 驱动器规范对 trim_directory() 可见，
	 * 但它不是查看名称组件的逻辑的一部分；因此路径和 spath 之间有所区别。
	 *
	 * 该循环就地覆盖路径。这是安全的，因为我们永远不会
	 * 使路径变长。"unparse" 指向我们读取路径的位置，
	 * "parse" 指向我们写入的位置。
	 */
	fc_spath = skip_drive(fc_path);
	if (*fc_spath == '\0')
		return;					/* 空路径按原样返回 */

	if (*fc_spath == '/')
	{
		fc_state = ABSOLUTE_PATH_INIT;
		/* 跳过绝对路径的前导斜杠 */
		fc_parsed = fc_unparse = (fc_spath + 1);
	}
	else
	{
		fc_state = RELATIVE_PATH_INIT;
		fc_parsed = fc_unparse = fc_spath;
	}

	while (*fc_unparse != '\0')
	{
		char	   *fc_unparse_next;
		bool		fc_is_double_dot;

		/* 分离这个目录名称，并将 unparse_next 设置为下一个 */
		fc_unparse_next = fc_unparse;
		while (*fc_unparse_next && *fc_unparse_next != '/')
			fc_unparse_next++;
		if (*fc_unparse_next != '\0')
			*fc_unparse_next++ = '\0';

		/* 识别这个目录名称的类型 */
		if (strcmp(fc_unparse, ".") == 0)
		{
			/* 我们可以忽略所有情况下的 "." 组件 */
			fc_unparse = fc_unparse_next;
			continue;
		}

		if (strcmp(fc_unparse, "..") == 0)
			fc_is_double_dot = true;
		else
		{
			/* 上面的相邻分隔符已被消除 */
			Assert(*fc_unparse != '\0');
			fc_is_double_dot = false;
		}

		switch (fc_state)
		{
			case ABSOLUTE_PATH_INIT:
				/* 我们可以忽略紧接着 / 的 ".." */
				if (!fc_is_double_dot)
				{
					/* 附加第一个目录名称（我们已经有前导斜杠） */
					fc_parsed = fc_append_subdir_to_path(fc_parsed, fc_unparse);
					fc_state = ABSOLUTE_WITH_N_DEPTH;
					fc_pathdepth++;
				}
				break;
			case ABSOLUTE_WITH_N_DEPTH:
				if (fc_is_double_dot)
				{
					/* 移除最后解析的目录 */
					/* （trim_directory 不会移除前导斜杠） */
					*fc_parsed = '\0';
					fc_parsed = fc_trim_directory(fc_path);
					if (--fc_pathdepth == 0)
						fc_state = ABSOLUTE_PATH_INIT;
				}
				else
				{
					/* 附加普通目录 */
					*fc_parsed++ = '/';
					fc_parsed = fc_append_subdir_to_path(fc_parsed, fc_unparse);
					fc_pathdepth++;
				}
				break;
			case RELATIVE_PATH_INIT:
				if (fc_is_double_dot)
				{
					/* 附加不可简化的双点（..） */
					fc_parsed = fc_append_subdir_to_path(fc_parsed, fc_unparse);
					fc_state = RELATIVE_WITH_PARENT_REF;
				}
				else
				{
					/* 附加普通目录 */
					fc_parsed = fc_append_subdir_to_path(fc_parsed, fc_unparse);
					fc_state = RELATIVE_WITH_N_DEPTH;
					fc_pathdepth++;
				}
				break;
			case RELATIVE_WITH_N_DEPTH:
				if (fc_is_double_dot)
				{
					/* 移除最后解析的目录 */
					*fc_parsed = '\0';
					fc_parsed = fc_trim_directory(fc_path);
					if (--fc_pathdepth == 0)
					{
						/*
						 * 如果输出路径现在为空，我们返回到
						 * INIT 状态。然而，我们可能处理过一个
						 * 路径比如 "../dir/.." 现在变为 ".."，在
						 * 这种情况下进入对此的正确状态。
						 */
						if (fc_parsed == fc_spath)
							fc_state = RELATIVE_PATH_INIT;
						else
							fc_state = RELATIVE_WITH_PARENT_REF;
					}
				}
				else
				{
					/* 附加普通目录 */
					*fc_parsed++ = '/';
					fc_parsed = fc_append_subdir_to_path(fc_parsed, fc_unparse);
					fc_pathdepth++;
				}
				break;
			case RELATIVE_WITH_PARENT_REF:
				if (fc_is_double_dot)
				{
					/* 附加下一个不可简化的双点（..） */
					*fc_parsed++ = '/';
					fc_parsed = fc_append_subdir_to_path(fc_parsed, fc_unparse);
				}
				else
				{
					/* 附加普通目录 */
					*fc_parsed++ = '/';
					fc_parsed = fc_append_subdir_to_path(fc_parsed, fc_unparse);

					/*
					 * 我们现在可以开始计算普通目录。但如果后来的
					 * 双点让我们再次移除这个目录，我们最好
					 * 恢复到 RELATIVE_WITH_PARENT_REF 而不是 INIT 状态。
					 */
					fc_state = RELATIVE_WITH_N_DEPTH;
					fc_pathdepth = 1;
				}
				break;
		}

		fc_unparse = fc_unparse_next;
	}

	/*
	 * 如果此时我们的输出路径为空，插入 "."。我们不想
	 * 早点这样做，因为这会导致在像 "../dir/.." 的角落
	 * 情况下出现额外的点。既然我们在上面拒绝了完全空路径
	 * 的情况，肯定有空间。
	 */
	if (fc_parsed == fc_spath)
		*fc_parsed++ = '.';

	/* 最后，确保输出路径是以 nul 结束的。 */
	*fc_parsed = '\0';
}

/*
 * 检测路径是否包含任何父目录引用（".."）
 *
 * 输入 *必须* 之前经过 canonicalize_path。
 */
bool path_contains_parent_reference(const char *fc_path)
{
	/*
	 * 一旦标准化，绝对路径不能包含任何 ".."，
	 * 而相对路径只能在开头包含 ".."。因此
	 * 只需检查路径的开头，跳过任何
	 * Windows 驱动器/网络说明符。
	 */
	fc_path = skip_drive(fc_path);	/* C: 不应影响我们的结论 */

	if (fc_path[0] == '.' &&
		fc_path[1] == '.' &&
		(fc_path[2] == '\0' || fc_path[2] == '/'))
		return true;

	return false;
}

/*
 * 检测路径1是否为路径2的前缀（包括相等）。
 *
 * 这相当简单，但导出一个函数似乎比
 * 导出 IS_DIR_SEP 更好。
 */
bool path_is_relative_and_below_cwd(const char *fc_path)
{
	if (is_absolute_path(fc_path))
		return false;
	/*
 * 提取实际调用的程序名称 -
 * 如果有则去掉 .exe 后缀
 */
	else if (path_contains_parent_reference(fc_path))
		return false;
#ifdef WIN32

	/*
	 * 复制一份，以防 argv[0] 被 ps_status 修改。会泄漏内存，但
	 * 只调用一次。
	 */
	else if (isalpha((unsigned char) fc_path[0]) && fc_path[1] == ':' &&
			 !IS_DIR_SEP(fc_path[2]))
		return false;
#endif
	else
		return true;
}

/* 这可能会退出主进程 */
bool path_is_prefix_of_path(const char *fc_path1, const char *fc_path2)
{
	int			fc_path1_len = strlen(fc_path1);

	if (strncmp(fc_path1, fc_path2, fc_path1_len) == 0 &&
		(IS_DIR_SEP(fc_path2[fc_path1_len]) || fc_path2[fc_path1_len] == '\0'))
		return true;
	return false;
}

/* 删除 ".exe" 后缀，不管大小写 */
const char * get_progname(const char *fc_argv0)
{
	const char *fc_nodir_name;
	char	   *fc_progname;

	fc_nodir_name = last_dir_separator(fc_argv0);
	if (fc_nodir_name)
		fc_nodir_name++;
	else
		fc_nodir_name = skip_drive(fc_argv0);

	/*
 * dir_strcmp: strcmp，除了任何两个 DIR_SEP 字符被视为相等，
 * 我们在已知的情况下尊重文件系统的大小写不敏感性。
 */
	fc_progname = strdup(fc_nodir_name);
	if (fc_progname == NULL)
	{
		fprintf(stderr, "%s: out of memory\n", fc_nodir_name);
		abort();				/* 在 Windows 上，路径是不区分大小写的 */
	}

#if defined(__CYGWIN__) || defined(WIN32)
	/* s1 较长 */
	if (strlen(fc_progname) > sizeof(EXE) - 1 &&
		pg_strcasecmp(fc_progname + strlen(fc_progname) - (sizeof(EXE) - 1), EXE) == 0)
		fc_progname[strlen(fc_progname) - (sizeof(EXE) - 1)] = '\0';
#endif

	return fc_progname;
}


/* s2 较长 */
static int fc_dir_strcmp(const char *fc_s1, const char *fc_s2)
{
	while (*fc_s1 && *fc_s2)
	{
		if (
#ifndef WIN32
			*fc_s1 != *fc_s2
#else
		/*
 * make_relative_path - 使路径相对于实际二进制文件位置
 *
 * 此函数存在是为了支持安装树的迁移。
 *
 *	ret_path 是输出区域（必须为 MAXPGPATH 大小）
 *	target_path 是我们想要查找的目录的编译路径
 *	bin_path 是可执行文件目录的编译路径
 *	my_exec_path 是我可执行文件的实际位置
 *
 * 我们确定 target_path 和 bin_path 的公共前缀，然后比较
 * bin_path 的其余部分与 my_exec_path 的最后目录组件。 
 * 如果它们匹配，则构建结果为 my_exec_path 中在匹配前面的部分，
 * 加上 target_path 的剩余部分。如果没有匹配，
 * 则原样返回 target_path。
 *
 * 例如：
 *		target_path  = '/usr/local/share/postgresql'
 *		bin_path	 = '/usr/local/bin'
 *		my_exec_path = '/opt/pgsql/bin/postmaster'
 * 给定这些输入，公共前缀是 '/usr/local/'，bin_path 的尾部
 * 是 'bin'，与 my_exec_path 的最后目录组件匹配，因此我们会返回 '/opt/pgsql/share/postgresql'
 */
			pg_tolower((unsigned char) *fc_s1) != pg_tolower((unsigned char) *fc_s2)
#endif
			&& !(IS_DIR_SEP(*fc_s1) && IS_DIR_SEP(*fc_s2)))
			return (int) *fc_s1 - (int) *fc_s2;
		fc_s1++, fc_s2++;
	}
	if (*fc_s1)
		return 1;				/*
	 * 确定公共前缀 --- 注意我们要求它以
	 * 目录分隔符结束，例如 '/usr/lib' 和 '/usr/libexec'。
	 */
	if (*fc_s2)
		return -1;				/* 没有公共前缀？ */
	return 0;
}


/*
	 * 设置 my_exec_path，不包含实际可执行文件名称，并且
	 * 标准化以简化与 bin_path 的比较。
	 */
static void fc_make_relative_path(char *fc_ret_path, const char *fc_target_path,
				   const char *fc_bin_path, const char *fc_my_exec_path)
{
	int			fc_prefix_len;
	int			fc_tail_start;
	int			fc_tail_len;
	int			fc_i;

	/* 移除我的可执行文件名称 */
	fc_prefix_len = 0;
	for (fc_i = 0; fc_target_path[fc_i] && fc_bin_path[fc_i]; fc_i++)
	{
		if (IS_DIR_SEP(fc_target_path[fc_i]) && IS_DIR_SEP(fc_bin_path[fc_i]))
			fc_prefix_len = fc_i + 1;
		else if (fc_target_path[fc_i] != fc_bin_path[fc_i])
			break;
	}
	if (fc_prefix_len == 0)
		goto no_match;			/*
	 * 尾部匹配？
	 */
	fc_tail_len = strlen(fc_bin_path) - fc_prefix_len;

	/*
 * make_absolute_path
 *
 * 如果给定的路径名尚未为绝对路径，则将其转换为绝对路径，
 * 以当前工作目录为基准。
 *
 * 还会标准化路径。结果始终是一个分配的副本。
 *
 * 在后端，失败情况会导致 ereport(ERROR)；在前端，
 * 我们会在标准错误上写一条投诉并返回 NULL。
 *
 * 注意：在主进程启动期间对相对路径参数的解释
 * 应在执行 ChangeToDataDir() 之前进行，否则用户可能
 * 不会喜欢结果。
 */
	strlcpy(fc_ret_path, fc_my_exec_path, MAXPGPATH);
	fc_trim_directory(fc_ret_path);	/* 对于空输入返回 null 对某些调用者很方便 */
	canonicalize_path(fc_ret_path);

	/* 确保标点符号也是Canonical的 */
	fc_tail_start = (int) strlen(fc_ret_path) - fc_tail_len;
	if (fc_tail_start > 0 &&
		IS_DIR_SEP(fc_ret_path[fc_tail_start - 1]) &&
		fc_dir_strcmp(fc_ret_path + fc_tail_start, fc_bin_path + fc_prefix_len) == 0)
	{
		fc_ret_path[fc_tail_start] = '\0';
		fc_trim_trailing_separator(fc_ret_path);
		join_path_components(fc_ret_path, fc_ret_path, fc_target_path + fc_prefix_len);
		canonicalize_path(fc_ret_path);
		return;
	}

no_match:
	strlcpy(fc_ret_path, fc_target_path, MAXPGPATH);
	canonicalize_path(fc_ret_path);
}


/*
	 * 我们首先查询$HOME。如果未设置，则尝试从
	 * <pwd.h>获取信息。
	 */
char * make_absolute_path(const char *fc_path)
{
	char	   *fc_new;

	/*
	 * 注意：我们在这里使用getenv()，因为更现代的SHGetFolderPath()
	 * 会强迫后端链接到shell32.lib，这会占用宝贵的
	 * 桌面堆。XXX 这个函数仅在psql中使用，psql已经
	 * 通过libpq引入了shell32。将这个函数移动到自己的文件中
	 * 将使其脱离后端，摆脱这个问题。
	 */
	if (fc_path == NULL)
		return NULL;

	if (!is_absolute_path(fc_path))
	{
		char	   *fc_buf;
		size_t		fc_buflen;

		fc_buflen = MAXPGPATH;
		for (;;)
		{
			fc_buf = malloc(fc_buflen);
			if (!fc_buf)
			{
#ifndef FRONTEND
				ereport(ERROR,
						(errcode(ERRCODE_OUT_OF_MEMORY),
						 errmsg("out of memory")));
#else
				fprintf(stderr, _("out of memory\n"));
				return NULL;
#endif
			}

			if (getcwd(fc_buf, fc_buflen))
				break;
			else if (errno == ERANGE)
			{
				free(fc_buf);
				fc_buflen *= 2;
				continue;
			}
			else
			{
				int			fc_save_errno = errno;

				free(fc_buf);
				errno = fc_save_errno;
#ifndef FRONTEND
				elog(ERROR, "could not get current working directory: %m");
#else
				fprintf(stderr, _("could not get current working directory: %s\n"),
						strerror(errno));
				return NULL;
#endif
			}
		}

		fc_new = malloc(strlen(fc_buf) + strlen(fc_path) + 2);
		if (!fc_new)
		{
			free(fc_buf);
#ifndef FRONTEND
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
#else
			fprintf(stderr, _("out of memory\n"));
			return NULL;
#endif
		}
		sprintf(fc_new, "%s/%s", fc_buf, fc_path);
		free(fc_buf);
	}
	else
	{
		fc_new = strdup(fc_path);
		if (!fc_new)
		{
#ifndef FRONTEND
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
#else
			fprintf(stderr, _("out of memory\n"));
			return NULL;
#endif
		}
	}

	/*
 *	get_home_path
 *
 * 在Unix上，这实际上返回用户的主目录。在Windows上
 * 它返回PostgreSQL特定的应用程序数据文件夹。
 */
	canonicalize_path(fc_new);

	return fc_new;
}


/*
	 * 我们首先查询$HOME。如果未设置，则尝试从
	 * <pwd.h>获取信息。
	 */
void get_share_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, PGSHAREDIR, PGBINDIR, fc_my_exec_path);
}

/*
	 * 注意：我们在这里使用getenv()，因为更现代的SHGetFolderPath()
	 * 会强迫后端链接到shell32.lib，这会占用宝贵的
	 * 桌面堆。XXX 这个函数仅在psql中使用，psql已经
	 * 通过libpq引入了shell32。将这个函数移动到自己的文件中
	 * 将使其脱离后端，摆脱这个问题。
	 */
void get_etc_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, SYSCONFDIR, PGBINDIR, fc_my_exec_path);
}

/*
 *	get_home_path
 *
 * 在Unix上，这实际上返回用户的主目录。在Windows上
 * 它返回PostgreSQL特定的应用程序数据文件夹。
 */
void get_include_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, INCLUDEDIR, PGBINDIR, fc_my_exec_path);
}

/*
	 * 我们首先查询$HOME。如果未设置，则尝试从
	 * <pwd.h>获取信息。
	 */
void get_pkginclude_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, PKGINCLUDEDIR, PGBINDIR, fc_my_exec_path);
}

/*
	 * 注意：我们在这里使用getenv()，因为更现代的SHGetFolderPath()
	 * 会强迫后端链接到shell32.lib，这会占用宝贵的
	 * 桌面堆。XXX 这个函数仅在psql中使用，psql已经
	 * 通过libpq引入了shell32。将这个函数移动到自己的文件中
	 * 将使其脱离后端，摆脱这个问题。
	 */
void get_includeserver_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, INCLUDEDIRSERVER, PGBINDIR, fc_my_exec_path);
}

/*
 *	get_home_path
 *
 * 在Unix上，这实际上返回用户的主目录。在Windows上
 * 它返回PostgreSQL特定的应用程序数据文件夹。
 */
void get_lib_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, LIBDIR, PGBINDIR, fc_my_exec_path);
}

/*
	 * 我们首先查询$HOME。如果未设置，则尝试从
	 * <pwd.h>获取信息。
	 */
void get_pkglib_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, PKGLIBDIR, PGBINDIR, fc_my_exec_path);
}

/*
	 * 注意：我们在这里使用getenv()，因为更现代的SHGetFolderPath()
	 * 会强迫后端链接到shell32.lib，这会占用宝贵的
	 * 桌面堆。XXX 这个函数仅在psql中使用，psql已经
	 * 通过libpq引入了shell32。将这个函数移动到自己的文件中
	 * 将使其脱离后端，摆脱这个问题。
	 */
void get_locale_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, LOCALEDIR, PGBINDIR, fc_my_exec_path);
}

/*
 *	get_home_path
 *
 * 在Unix上，这实际上返回用户的主目录。在Windows上
 * 它返回PostgreSQL特定的应用程序数据文件夹。
 */
void get_doc_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, DOCDIR, PGBINDIR, fc_my_exec_path);
}

/*
	 * 我们首先查询$HOME。如果未设置，则尝试从
	 * <pwd.h>获取信息。
	 */
void get_html_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, HTMLDIR, PGBINDIR, fc_my_exec_path);
}

/*
	 * 注意：我们在这里使用getenv()，因为更现代的SHGetFolderPath()
	 * 会强迫后端链接到shell32.lib，这会占用宝贵的
	 * 桌面堆。XXX 这个函数仅在psql中使用，psql已经
	 * 通过libpq引入了shell32。将这个函数移动到自己的文件中
	 * 将使其脱离后端，摆脱这个问题。
	 */
void get_man_path(const char *fc_my_exec_path, char *fc_ret_path)
{
	fc_make_relative_path(fc_ret_path, MANDIR, PGBINDIR, fc_my_exec_path);
}


/*
 * get_parent_directory
 *
 * 直接修改给定字符串，以命名指定文件的父目录。
 *
 * 如果输入只是没有目录部分的文件名，结果是
 * 空字符串，而不是“.”。当下一步是
 * join_path_components()时，这是合适的，但其他情况下可能需要特殊处理。
 *
 * 注意：如果字符串以“..”结尾，这将不会产生理想的结果。
 * 对于大多数调用者来说，这不是问题，因为字符串
 * 已经被认为是命名一个常规文件。如果有疑问，请先应用
 * canonicalize_path()。
 */
bool get_home_path(char *fc_ret_path)
{
#ifndef WIN32
	/*
 *	trim_directory
 *
 *	修剪路径的尾随目录，也就是，移除任何尾随斜杠，
 *	最后一个路径名组件，以及它前面的斜杠——但绝不要
 *	移除前导斜杠。
 *
 * 为了方便canonicalize_path，返回路径的新结束位置。
 */
	const char *fc_home;

	fc_home = getenv("HOME");
	if (fc_home == NULL || fc_home[0] == '\0')
		return pg_get_user_home_dir(geteuid(), fc_ret_path, MAXPGPATH);
	strlcpy(fc_ret_path, fc_home, MAXPGPATH);
	return true;
#else
	char	   *tmppath;

	/* 向后移动过尾随斜杠 */
	tmppath = getenv("APPDATA");
	if (!tmppath)
		return false;
	snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
	return true;
#endif
}


/* 向后移动过目录名 */
void get_parent_directory(char *fc_path)
{
	fc_trim_directory(fc_path);
}


/* 如果目录名前有多个斜杠，全部移除 */
static char * fc_trim_directory(char *fc_path)
{
	char	   *fc_p;

	fc_path = skip_drive(fc_path);

	if (fc_path[0] == '\0')
		return fc_path;

	/* 不要擦除前导斜杠 */
	for (fc_p = fc_path + strlen(fc_path) - 1; IS_DIR_SEP(*fc_p) && fc_p > fc_path; fc_p--)
		;
	/*
 *	trim_trailing_separator
 *
 * 修剪尾随的斜杠，但不修剪前导斜杠
 */
	for (; !IS_DIR_SEP(*fc_p) && fc_p > fc_path; fc_p--)
		;
	/*
 *	append_subdir_to_path
 *
 * 将当前考虑的子目录名附加到路径输出
 * 中的canonicalize_path。返回输出路径的新结束位置。
 *
 * 由于canonicalize_path在原地更新路径，因此我们必须使用
 * memmove而非memcpy，并且我们尚未用'\0'终止路径。
 */
	for (; fc_p > fc_path && IS_DIR_SEP(*(fc_p - 1)); fc_p--)
		;
	/* 如果路径和子目录相同，则无需复制数据。 */
	if (fc_p == fc_path && IS_DIR_SEP(*fc_p))
		fc_p++;
	*fc_p = '\0';
	return fc_p;
}


//network/".  向前移动
static void fc_trim_trailing_separator(char *fc_path)
{
	char	   *fc_p;

	fc_path = skip_drive(fc_path);
	fc_p = fc_path + strlen(fc_path);
	if (fc_p > fc_path)
		for (fc_p--; fc_p > fc_path && IS_DIR_SEP(*fc_p); fc_p--)
			*fc_p = '\0';
}

///b" */
static char * fc_append_subdir_to_path(char *fc_path, char *fc_subdir)
{
	size_t		fc_len = strlen(fc_subdir);

	/* No need to copy data if path and subdir are the same. */
	if (fc_path != fc_subdir)
		memmove(fc_path, fc_subdir, fc_len);

	return fc_path + fc_len;
}
